import datetime
import json
import unicodedata
from collections import defaultdict
from functools import cache

import tushare as ts
from django.db.models import Count, F, Q, Sum
from django.http import JsonResponse

from monitor.utils import rest_queryset, filter_queryset, achievement_filter
from monitor.tasks import TASKS
from processor.connection import check_config, check_table_if_exists
from processor.exporter import EXPORT_TYPES, EXPORTERS
from processor.scheduler import Scheduler
from processor.models import (
    KeywordCombination, Shareholding, StockHolderNumber, StockAchievement, 
    Logger, StockForecast, FundPortfolio, ConsensusForecasts, ConceptQuarter,
    StockFreshPrice, ConceptStockDaily, ConceptDaily, StockConceptHistory,
    ConceptHistory, RSIStructure, RSITrend, RSIExtremum,
)


EMPTY_RESP = JsonResponse({})

def msg_resp(code, msg, others={}):
    return JsonResponse({
        'code': code, 
        'msg': msg,
        ** others,
    })


def source(request): 
    if request.method == 'GET':
        scode = request.GET.get('scode', '')
        today = datetime.date.today()
        
        df = ts.pro_bar(
            ts_code=scode,
            start_date=str(
                today - datetime.timedelta(days=730)
            ),
            ma=[5, 10, 20],
        )
        df = df[['open', 'close', 'low', 'high', 'vol', 
                 'ma5', 'ma10', 'ma20', 'trade_date']]
        
        df['trade_date'] = df['trade_date'].apply(
            lambda x: x[:4]+'-'+x[4:6]+'-'+x[6:])

        data = df.fillna(0)[::-1].to_dict('list')

        resp_data = {
            'total': len(data),
            'rows': data,
        }

        return JsonResponse(resp_data)

    return EMPTY_RESP


def manual_execute(request):
    if request.method == 'POST':
        task_name = request.POST.get('task_select', '')
        execute_type = request.POST.get('execute_type', '')
        execute_date = request.POST.get('execute_date', '')

        try:
            if execute_type == "now":
                execute_date = 0
            else:
                execute_date = datetime.datetime.strptime(execute_date, "%Y-%m-%d %H:%M:%S")
        except Exception as e:
            Logger.warning(msg=f"手动执行任务失败，日期不合法: {execute_date}。", module='scheduler')
            return msg_resp('1071', f"手动执行任务失败，日期不合法: {execute_date}。")

        if task_name not in TASKS:
            return msg_resp('1072', f'任务添加失败，错误：任务 {task_name} 不存在。')
        
        try:
            task_kwargs = {}
            for arg in TASKS[task_name]['args']:
                if arg['source'] == 'client':
                    task_kwargs[arg['name']] = request.POST.get(arg['name'], '')
                elif arg['source'] == 'server' and arg['name'] == 'callback':
                    task_kwargs[arg['name']] = Scheduler.update_progress

            Scheduler.set_timer(
                delay=execute_date,  
                desp=TASKS[task_name]['desp'],
                func=TASKS[task_name]['func'],
                kwargs=task_kwargs,
            )
        except Exception as e:
            Logger.error(msg=f"手动执行任务失败，错误：{e}", module='scheduler')
            return msg_resp('1073', f"任务添加失败，错误：{e}")
        
        return msg_resp('0', '任务添加成功！')

    return EMPTY_RESP


def update_progress(_):
    get_prog = lambda x: round(x[0] * 100 / x[1], 2)
    task = Scheduler.running_task[0] if Scheduler.running_task else None
    resp_data = {
        'running': Scheduler.keep_running,
        'proc_task': task[0] if task else "暂无",
        'task_start_time': "启动时间：" + task[1] if task else "",
        'proc_prog': get_prog(Scheduler.running_progress),
        'task_queue': [
            {
                'name': task[1],
                'func': task[3].__name__,
                'args': str(task[4]),
                'kwargs': str(task[5]),
                'start_time': "启动时间：" + datetime.datetime.strftime(task[0], r'%Y-%m-%d %H:%M:%S'),
            } for task in Scheduler.task_queue[:3]
        ]
    }
    return msg_resp('0', '获取成功！', resp_data)


def top10_holder_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            Shareholding.objects.values(
                'scode', 'sname', 'industry', 'concepts'
            ).annotate(
                new_holder_count=Count(1, filter=Q(change_type=2)),
                new_holder_prop=Sum('hold_prop', filter=Q(change_type=2)),
                add_holder_count=Count(1, filter=Q(change_type=1)),
                add_holder_prop=Sum('prop_change', filter=Q(change_type=1)),
                reduce_holder_count=Count(1, filter=Q(change_type=-1)),
                reduce_holder_prop=Sum('prop_change', filter=Q(change_type=-1)),
            ),
            request,
            filter_fields=(
                'quarter', 
            ),
            search_fields=(
                'scode', 'sname', 'industry', 'concepts', 'holder'
            )
        )
        
        return JsonResponse(resp_data)


@cache
def get_quarter_holders(quarter):
    return list(Shareholding.objects.filter(
        quarter=quarter
    ).values_list(
        'holder', flat=True
    ).distinct())


def combine_search_holders(combine_id, quarter):
    def _keyword_filter(_list, keywords, exclude_keywords):
        return [
            _str for _str in _list
            if any(key in _str for key in keywords)
            if not any(key in _str for key in exclude_keywords)
        ]
    
    try:
        keywords, exclude_keywords = KeywordCombination.objects.filter(
            id=combine_id
        ).values_list(
            'keyword_list', 'exclude_keyword_list'
        ).first()

        return _keyword_filter(
            get_quarter_holders(quarter),
            keywords, 
            exclude_keywords
        )
    except Exception as e:
        return []    


def combined_shareholding_data(request):
    if request.method == 'GET':
        quarter = request.GET.get('quarter', Shareholding.objects.latest('quarter').quarter)
        last_quarter = Shareholding.objects.filter(quarter__lt=quarter).latest('quarter').quarter

        resp_data = {'total': KeywordCombination.objects.count(), 'rows': []}

        for combine_name, combine_id in KeywordCombination.objects.values_list(
            'cname', 'id'
        ):
            matched_holders = combine_search_holders(combine_id, quarter)
            last_quarter_matched_holders = combine_search_holders(combine_id, last_quarter)

            this_quarter_combine = Shareholding.objects.filter(
                quarter=quarter, 
                holder__in=matched_holders
            )

            row = {
                'combine_id': combine_id,
                'combine_name': combine_name,
                **Shareholding.objects.filter(
                    quarter=last_quarter,
                    holder__in=last_quarter_matched_holders
                ).aggregate(
                    last_quarter_count=Count('stock_basic', distinct=True),
                    last_quarter_hold_amount=Sum('hold_amount', default=0),
                ),
                **this_quarter_combine.aggregate(
                    count=Count('stock_basic', distinct=True),
                    hold_amount=Sum('hold_amount', default=0),
                ),
                'added_list': list(
                    this_quarter_combine.filter(
                        change_type=1
                    ).values_list('sname', flat=True).distinct()
                ),
                'reduced_list': list(
                    this_quarter_combine.filter(
                        change_type=-1
                    ).values_list('sname', flat=True).distinct()
                ),
                'fixed_list': list(
                    this_quarter_combine.filter(
                        change_type=0
                    ).values_list('sname', flat=True).distinct()
                ),
            }
            
            resp_data['rows'].append(row)
        
        return JsonResponse(resp_data)


def combined_stock_shareholding_data(request):
    if request.method == 'GET':
        quarter = request.GET.get('quarter')
        combine_id = request.GET.get('combine_id')

        matched_holders = combine_search_holders(combine_id, quarter)

        resp_data = rest_queryset(
            Shareholding.objects.filter(
                holder__in=matched_holders,
            ).values(
                'scode', 'sname', 'industry', 'concepts'
            ).annotate(
                hold_prop=Sum('hold_prop'),
                hold_amount=Sum('hold_amount'),
                prev_hold_prop=Sum('prev_hold_prop'),
                prev_hold_amount=Sum('prev_hold_amount'),
            ),
            request,
            filter_fields=(
                'quarter', 
            ),
        )
        
        return JsonResponse(resp_data)


def combine_keywords_data(request):
    def split_keywords(txt):
        txt = unicodedata.normalize('NFKC', txt)
        return [
            key.strip()
            for key in txt.split(',')
            if key.strip()
        ]
    
    if request.method == 'GET':
        resp_data = rest_queryset(
            KeywordCombination.objects.all(),
            request,
        )
        
        return JsonResponse(resp_data)

    if request.method == 'POST':
        data = json.loads(request.body)
        keywords = data.get('keywords', [])
        if not isinstance(keywords, list):
            keywords = []

        keywords = [
            keyword
            for keyword in keywords
            if keyword.get('name', '')
        ]

        print(keywords)
        KeywordCombination.objects.all().delete()
        KeywordCombination.objects.bulk_create([
            KeywordCombination(
                cname=row['name'],
                keyword_list=split_keywords(row.get('keywords', '')),
                exclude_keyword_list=split_keywords(row.get('excludeKeywords', '')),
            )
            for row in keywords
        ])

        return JsonResponse({})


def legend_holder_shareholding_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            Shareholding.objects.filter(
                is_legend=True
            ).values(
                'holder', 
            ).annotate(
                count=Count(1),
                hold_amount=Sum('hold_amount'),
            ),
            request,
            filter_fields=(
                'quarter', 
            ),
        )
        
        return JsonResponse(resp_data)


def stock_legend_holder_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            Shareholding.objects.filter(
                is_legend=True
            ).values(
                'scode', 'sname', 'industry', 'concepts'
            ).annotate(
                count=Count(1),
            ),
            request,
            filter_fields=(
                'quarter', 
            ),
        )
        
        return JsonResponse(resp_data)


def shareholding_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            Shareholding.objects.all(),
            request,
            filter_fields=(
                'quarter', 'holder', 'scode', 'is_legend'
            ),
        )
        
        return JsonResponse(resp_data)


def holder_number_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            StockHolderNumber.objects.all(),
            request,
            search_fields=(
                'scode', 'sname', 'industry'
            )
        )

        return JsonResponse(resp_data)
    
    return EMPTY_RESP


def achievement_data(request):
    if request.method == 'GET':
        queryset = StockAchievement.objects.all()
        queryset = achievement_filter(queryset, request)

        resp_data = rest_queryset(
            queryset,
            request,
            filter_fields=(
                'quarter', 'stock_basic', 'scode'
            ),
            search_fields=(
                'scode', 'sname', 'industry', 'quarter'
            )
        )

        return JsonResponse(resp_data)

    return EMPTY_RESP


def log_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            Logger.objects.all(),
            request,
            filter_fields=['level', 'module'],
        )

        return JsonResponse(resp_data)
    
    return EMPTY_RESP


def export_data(request):
    if request.method == 'POST':
        source_select = request.POST.get('source_select', '')
        write_type = request.POST.get('write_type', 'fail')
        db_address = request.POST.get('db_address', '')
        db_port = request.POST.get('db_port', '')
        db_name = request.POST.get('db_name', '')
        db_table = request.POST.get('db_table', '')
        db_user = request.POST.get('db_user', '')
        db_pass = request.POST.get('db_pass', '')

        ok, msg, conn = check_config(db_address, db_port, db_user, db_pass, db_name)
        
        if not ok:
            return msg_resp('1121', msg)
        
        if write_type == 'fail' and check_table_if_exists(conn, db_name, db_table):
            return msg_resp('1122', f"当前表名 `{db_table}` 已存在！")
        
        elif write_type not in EXPORT_TYPES:
            return msg_resp('1123', f"写入类型不合法！")

        elif source_select not in EXPORTERS:
            return msg_resp('1124', f"数据类型不合法！")

        source = EXPORTERS[source_select]['class']()

        try:
            source.to_db(db_address, db_port, db_user, db_pass, db_name, db_table, write_type)
        except Exception as e:
            Logger.error(msg=f"导出任务失败，错误：{e}", module='export')
            return msg_resp('1125', f"导出任务失败！{e}")
        
        return msg_resp('0', "任务添加成功！")

    return EMPTY_RESP


def stock_forecast_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            StockForecast.objects.all(),
            request,
            filter_fields=(
                'quarter', 'stock_basic', 'scode'
            ),
            search_fields=(
                'scode', 'sname', 'industry', 'quarter', 'type'
            )
        )

        return JsonResponse(resp_data)
    
    return EMPTY_RESP


def fund_portfolio_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            FundPortfolio.objects.all(),
            request,
            filter_fields=(
                'quarter', 'stock_basic', 'scode', 'fcode'
            ),
            search_fields=(
                'scode', 'sname', 'industry', 'quarter', 'fcode', 'fname', 'fund_type', 
            )
        )

        return JsonResponse(resp_data)
    
    return EMPTY_RESP


def consensus_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            ConsensusForecasts.objects.all().annotate(
                fy1_to_fy0=F('net_profit_fy1')/F('net_profit_fy0'),
                fy2_to_fy1=F('net_profit_fy2')/F('net_profit_fy1'),
            ),
            request,
            filter_fields=(
                'quarter', 'stock_basic', 'scode', 'fcode'
            ),
            search_fields=(
                'scode', 'sname', 'industry', 'quarter', 
            )
        )

        return JsonResponse(resp_data)
    
    return EMPTY_RESP


def quarter_concept_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            ConceptQuarter.objects.all().annotate(
                # fy1_to_fy0=F('net_profit_fy1')/F('net_profit_fy0'),
                # fy2_to_fy1=F('net_profit_fy2')/F('net_profit_fy1'),
                concept_code=F('stock_concept__ccode'),
                concept_name=F('stock_concept__cname'),
                stock_basic_count=F('stock_concept__stock_basic_count'),
            ),
            request,
            filter_fields=(
                'quarter', 'stock_concept', 
            ),
            search_fields=(
                'stock_concept__ccode', 'stock_concept__cname', 'stock_basic_count'
            )
        )

        return JsonResponse(resp_data)
    
    return EMPTY_RESP


def fresh_price_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            StockFreshPrice.objects.all(),
            request,
            search_fields=('scode', 'sname', 'industry'),
        )
    
        return JsonResponse(resp_data)

    return EMPTY_RESP


def concept_stock_daily_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            ConceptStockDaily.objects.all(),
            request,
            filter_fields=('trade_date', ),
            search_fields=('ccode', 'cname', 'stock_basic_count'),
        )
    
        return JsonResponse(resp_data)

    return EMPTY_RESP


def concept_daily_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            ConceptDaily.objects.all(),
            request,
            filter_fields=('trade_date', ),
            search_fields=('ccode', 'cname', 'stock_basic_count'),
        )
    
        return JsonResponse(resp_data)

    return EMPTY_RESP


def stock_concept_history_data(request):
    if request.method == 'GET':
        resp_data = rest_queryset(
            StockConceptHistory.objects.all(),
            request,
            filter_fields=('source', 'update_date', ),
            search_fields=('ccode', 'cname'),
        )   

        resp_data['info'] = filter_queryset(
            ConceptHistory.objects.all(), 
            request, 
            fields=('source', 'update_date'),
        ).values().first()
        
        return JsonResponse(resp_data)

    return EMPTY_RESP


def update_wind_concept_data(request):
    if request.method == 'POST':
        req_data = json.loads(request.body)
        data = req_data.get('keywords', [])
        trade_date = req_data.get('trade_date')
        concepts = defaultdict(list)
        for row in data:
            for concept in row['所属概念板块'].split('/'):
                concepts[concept].append({
                    'code': row['证券代码'],
                    'name': row['证券简称'],
                })
        for concept, stocks_list in concepts.items():
            StockConceptHistory.objects.record(
                source=2,
                ccode=concept,
                cname=concept,
                stocks_list=stocks_list,
                update_date=trade_date,
            )
        return JsonResponse({'msg': 'ok'})


def struct_history_data(request):
    '''
        RSI数据分析 - 周期数据分析
    '''
    if request.method == 'GET':
        resp_data = rest_queryset(
            RSIStructure.objects.values_list(
                'sdate', 'percentage',
            ),
            request,
        )

        return JsonResponse(resp_data)
    

def trend_date_select_data(request):
    '''
        RSI数据分析 - 日级/周级趋势交集 - 日期选项
    '''
    if request.method == 'GET':
        resp_data = rest_queryset(
            RSITrend.objects.values_list(
                'fdate', flat=True
            ).distinct(),
            request,
            filter_fields=['period'],
        )

        return JsonResponse(resp_data)
    

def trend_list_data(request):
    '''
        RSI数据分析 - 日级/周级趋势交集
    '''
    if request.method == 'GET':
        queryset = filter_queryset(
            RSITrend.objects,
            request,
            fields=['period', 'fdate'],
            defaults={'period': 1},
        ).order_by(
            '-fdate'
        )

        rsi_trend = queryset.first()
        collector = defaultdict(dict)

        for trend in ['bg_a', 'bg_b', 'bg_c', 'bg_d', 'bg_e', 'bg_f']:
            for stock in getattr(rsi_trend, trend):
                collector.setdefault(stock['scode'], {
                    'scode': stock['scode'],
                    'sname': stock['sname'],
                    'industry': stock['industry'],
                    'mixed': []
                })
                collector[stock['scode']]['mixed'].append(trend[-1].upper())
                collector[stock['scode']]

        for scode, stock_info in list(collector.items()):
            if len(stock_info['mixed']) <= 1:
                collector.pop(scode)

        return JsonResponse({
            'total': len(collector),
            'rows': list(collector.values()),
        })
    

'''
def extre_date_select_data(request):
    # RSI数据分析 - 历史/范围最大最小值 - 日期选项
    
    if request.method == 'GET':
        field = request.GET.get('field', '')

        resp_data = rest_queryset(
            RSIExtremum.objects.values_list(
                f'{field}_date', flat=True
            ).distinct(),
            request,
            filter_fields=['period'],
        )

        return JsonResponse(resp_data)
    

def extre_data(request):
    # RSI数据分析 - 历史/范围最大最小值
    if request.method == 'GET':
        field = request.GET.get('field', '')
        date = request.GET.get('date', '')

        resp_data = rest_queryset(
            RSIExtremum.objects.filter(
                **{f'{field}_date': date},
            ).values_list(
                'scode', 'sname', 'industry', 'lastest_rsi', 
                value=F(field), 
            ),
            request,
            filter_fields=['period'],
        )

        return JsonResponse(resp_data)
    
'''